package Gradle基础;


●Task 

Gradle 中的所有内容都基于两个基本概念：项目和任务。

每个 Gradle 构建都由一个或多个项目组成。项目代表什么取决于您在 Gradle 中所做的事情。例如，一个项目可能代表一个 JAR 库或一个 Web 应用程序。它可能表示从其他项目
产生的 JAR 组装而成的发行版 ZIP。项目不一定代表要构建的事物。它可能表示要完成的事情，例如将应用程序部署到暂存或生产环境。暂时不要担心这似乎还不清楚。Gradle 的
按惯例构建支持为项目的定义添加了更具体的定义。

每个项目由一个或多个任务组成。任务代表构建执行的一些原子工作。这可能是编译某些类，创建 JAR，生成 Javadoc 或将一些存档发布到存储库。

现在，将研究在一个项目中构建一些简单的任务。后面的章节将介绍处理多个项目，以及有关处理项目和任务的更多信息。

●Hello world 
同样，也是先从 hello world 入门。您可以使用以下 gradle 命令运行 Gradle 构建。该 gradle 命令在当前目录中查找名为 build.gradle 的文件。
这个 build.gradle 文件称为构建脚本，尽管严格来说，它是一个构建配置脚本，我们将在后面看到。构建脚本定义项目及其任务。

要尝试此操作，请创建以下名为的构建脚本 build.gradle。

例子1. 第一个构建脚本，在 android 项目中找到 build.gradle，在里面添加以下代码：

// build.gradle
task hello {
    doLast {
        println 'Hello world!'
    }
}

在项目的移至包含的目录并使用以下命令执行构建脚本：

./gradlew -q hello // Android 用户在根目录使用 ./gradlew
gradle -q hello  // 非 Android 用户使用 gradle
使用 -q 命令行选项运行。这将取消 Gradle 的日志消息，因此仅显示任务的输出。这样可以使示例输出更加清晰。如果不想，则不需要使用此选项。

后面直接将执行构建脚本的命令放在注释前，不在单行作为展示了。

●定义任务
在这里，你将看到了如何使用字符串作为任务名称来定义任务。此样式有一些变体，您可能需要在某些情况下使用。


task('hello') {
    doLast {
        println "hello"
    }
}

task('copy', type: Copy) {
    from(file('srcDir'))
    into(buildDir)
}

上面创建了两个任务分别是 hello 和 copy。有一种定义任务的替代语法，您可能更喜欢使用：

tasks.create('hello') {
    doLast {
        println "hello"
    }
}

tasks.create('copy', Copy) {
    from(file('srcDir'))
    into(buildDir)
}

上面同样创建了两个任务分别是 hello 和 copy。 最后，Groovy 和 Kotlin DSL 有特定于语言的语法：


// Using Groovy dynamic keywords

task(hello) {
    doLast {
        println "hello"
    }
}

task(copy, type: Copy) {
    from(file('srcDir'))
    into(buildDir)
}

●采用代码构建脚本
Gradle 的构建脚本为您提供了 Groovy 和 Kotlin 的全部功能。作为一个开胃菜，看看这个：在Gradle的任务中使用 Groovy 或 Kotlin ：

//gradle -q upper
task upper {
    doLast {
        String someString = 'mY_nAmE'
        println "Original: $someString"
        println "Upper case: ${someString.toUpperCase()}"
    }
}

//gradle -q count
task count {
    doLast {
        4.times { print "$it " }
    }
}
 

●任务之间的依赖
任务之间可以具有依赖性，关键字 dependsOn ：

// gradle -q intro
task hello {
    doLast {
        println 'Hello world!'
    }
}
task intro {
    dependsOn hello
    doLast {
        println "I'm Gradle"
    }
}

上面的依赖是依赖的任务先声明，然后再进行依赖，还有一种依赖是懒惰性依赖，被依赖的任务可以后面再声明，但是如果不声明的会报错：

// gradle -q taskX 
task taskX {
    dependsOn 'taskY'
    doLast {
        println 'taskX'
    }
}
task taskY {
    doLast {
        println 'taskY'
    }
}

任务 taskX 依赖的任务 taskY 是后声明的。

您可以通过多种方式定义任务的依赖关系。在“ 任务依赖项”中，介绍了使用任务名称定义依赖项。任务名称可以引用与该任务在同一项目中的任务，也可以引用其他项目中的任务。
要引用另一个项目中的任务，请在任务名称前添加其所属项目的路径。以下是添加从 projectA:taskX 到的依赖的示例 projectB:taskY：

// gradle -q taskX

project('projectA') {
    task taskX {
        dependsOn ':projectB:taskY'
        doLast {
            println 'taskX'
        }
    }
}

project('projectB') {
    task taskY {
        doLast {
            println 'taskY'
        }
    }
}

此处的 projectA，projectB 要改成你项目中的名字，简单来说，就是不同层级的任务也是可以相互依赖的。

●动态任务
Groovy 或 Kotlin 的功能可用于定义任务以外的其他功能。例如，您也可以使用它来动态创建任务。

// gradle -q task1
4.times { counter ->
    task "task$counter" {
        doLast {
            println "I'm task number $counter"
        }
    }
}

 上述创建了 4 个 task，分别是 task0，task1，task2，task3。

●操作已创建的任务
任务创建后，就可以通过 API 对其进行访问。例如，您可以在运行时为任务动态添加依赖项。

// gradle -q task0
4.times { counter ->
    task "task$counter" {
        doLast {
            println "I'm task number $counter"
        }
    }
}
task0.dependsOn task2, task3

●或者，您可以将行为添加到现有任务。

// gradle -q hello
task hello {
    doLast {
        println 'Hello Earth'
    }
}
hello.doFirst {
    println 'Hello Venus'
}
hello.configure {
    doLast {
        println 'Hello Mars'
    }
}
hello.configure {
    doLast {
        println 'Hello Jupiter'
    }
}

调用 doFirst 和 doLast 可以执行多次。他们将操作添加到任务操作列表的开头或结尾。执行任务时，将按顺序执行操作列表中的操作。

●Groovy DSL快捷方式符号
访问现有任务有一种方便的表示法。每个任务都可以作为构建脚本的属性来使用：

// gradle -q hello
task hello {
    doLast {
        println 'Hello world!'
    }
}
hello.doLast {
    println "Greetings from the $hello.name task."
}

例子中，通过获取任务的名字可以知道这个是来自于 task hello 的任务所做的事。这样可以提高代码的可读性，尤其是在使用插件提供的任务（例如compile任务）时。

●额外任务属性
您可以将自己的属性添加到任务上。要添加名为的属性 myProperty，并为 ext.myProperty 设置初始值。就可以像预定义的任务属性一样读取和设置属性。

// gradle -q printTaskProperties
task myTask {
    ext.myProperty = "myValue"//ext为保留字，如同task
}

task printTaskProperties {
    doLast {
        println myTask.myProperty
    }
}

●默认任务
如果未指定其他任务，则Gradle允许您定义一个或多个默认任务。

// gradle -q
defaultTasks 'clean', 'run'

task clean {
    doLast {
        println 'Default Cleaning!'
    }
}

task run {
    doLast {
        println 'Default Running!'
    }
}

task other {
    doLast {
        println "I'm not a default task!"
    }
}

这等效于运行 gradle clean run。在多项目构建中，每个子项目都可以有其自己的特定默认任务。如果子项目未指定默认任务，则使用父项目的默认任务（如果已定义）

●通过DAG进行配置
正如我们稍后将详细描述的（请参阅 Build Lifecycle），Gradle具有配置阶段和执行阶段。在配置阶段之后，Gradle 知道应该执行的所有任务。Gradle 为您提供了
一个利用此信息的机会。一个用例是检查发布任务是否在要执行的任务中。以此为基础，您可以为某些变量分配不同的值。

在以下示例中，distribution 和 release 任务的执行导致 version 变量的值不同。


// gradle -q distribution
// gradle -q release
task distribution {
    doLast {
        println "We build the zip with version=$version"
    }
}

task release {
    dependsOn 'distribution'
    doLast {
        println 'We release now'
    }
}

gradle.taskGraph.whenReady { taskGraph ->
    if (taskGraph.hasTask(":release")) {
        version = '1.0'
    } else {
        version = '1.0-SNAPSHOT'
    }
}

可以发现，此处，执行不同的 task 具有不同结果。

●构建脚本的外部依赖关系
如果构建脚本需要使用外部库，则可以将它们添加到构建脚本本身中的脚本的类路径中。您可以使用 buildscript() 方法执行此操作，并传入一个声明构建脚本类路径的块。

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
    }
}

 buildscript()方法中的代码块将构成 ScriptHandler 实例。您可以通过向 classpath 配置添加依赖项来声明构建脚本类路径。这与您声明 Java 编译类路径的
 方式相同。您可以使用除项目依赖项以外的任何 依赖项类型(Dependency Types)。

声明了构建脚本类路径后，就可以像在该类路径上的任何其他类一样使用构建脚本中的类。以下示例将添加到前面的示例中，并使用构建脚本类路径中的类。

gradle -q encode
import org.apache.commons.codec.binary.Base64

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
    	//org.apache.commons.codec.binary.Base64
        classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
    }
}

task encode {
    doLast {
        def byte[] encodedString = new Base64().encode('hello world\n'.getBytes())
        println new String(encodedString)
    }
}

这里先是添加了依赖项，然后再创建了一个任务，引用了依赖项中的类来实现对字符串的加密。

●访问任务的属性
您通常需要找到在构建文件中定义的任务，例如，对其进行配置或将其用于依赖项。有很多方法可以做到这一点。首先，就像定义任务一样，Groovy和Kotlin DSL具有特定于语言的语法

task hello
task copy(type: Copy)

// Access tasks using Groovy dynamic properties on Project

println hello.name
println project.hello.name

println copy.destinationDir
println project.copy.destinationDir

任务也可以通过 tasks 集合获得。

task hello
task copy(type: Copy)

println tasks.hello.name
println tasks.named('hello').get().name

println tasks.copy.destinationDir
println tasks.named('copy').get().destinationDir

您可以使用 tasks.getByPath() 方法使用任务的路径从任何项目访问任务。您可以 getByPath() 使用任务名称，相对路径或绝对路径来调用该方法。


project(':projectA') {
    task hello
}

task hello

println tasks.getByPath('hello').path
println tasks.getByPath(':hello').path
println tasks.getByPath('projectA:hello').path
println tasks.getByPath(':projectA:hello').path

●将参数传递给任务构造函数
与 Task 在创建后配置变量的可变属性相反，您可以将参数值传递给 Task 类的构造函数。为了将值传递给 Task 构造函数，您必须使用注释相关的构造
函数 @javax.inject.Inject。

class CustomTask extends DefaultTask {
    final String message
    final int number

    @Inject
    CustomTask(String message, int number) {
        this.message = message
        this.number = number
    }
}

然后，您可以创建一个任务，并在参数列表的末尾传递构造函数参数。

tasks.create('myTask', CustomTask, 'hello', 42)
task myTask(type: CustomTask, constructorArgs: ['hello', 42])
上述两种方法都可以。在所有情况下，作为构造函数参数传递的值都必须为非 null。如果您尝试传递一个 null 值，Gradle 将抛出一个 NullPointerException 指示，
指出哪个运行时值是 null。

●向任务添加描述
您可以在任务中添加描述。执行 gradle tasks 时将显示此描述。

// gradle tasks
task copy(type: Copy) {
   description 'Copies the resource directory to the target directory.'
   from 'resources'
   into 'target'
   include('**/*.txt', '**/*.xml', '**/*.properties')
}

●更换任务
有时您想替换任务。例如，如果要将 Java 插件添加的任务与其他类型的自定义任务交换。您可以使用以下方法实现此目的：

// gradle -q copy
task copy(type: Copy)

task copy(overwrite: true) {
    doLast {
        println('I am the new one.')
    }
}

定义新任务时，必须将 overwrite 属性设置为 true。否则，Gradle 会引发异常，说该名称的任务已经存在。

●跳过任务
Gradle 提供了多种方法来跳过任务的执行。

◾使用谓词
您可以使用该 onlyIf() 方法将谓词附加到任务。仅当谓词评估为 true 时，才执行任务的动作。您将谓词实现为闭包。闭包作为参数传递给任务，如果任务应执行，
则应返回 true；如果应跳过任务，则应返回 false。在即将执行任务之前就对谓词进行评估。

// 
gradle hello -PskipHello

task hello {
    doLast {
        println 'hello world'
    }
}

hello.onlyIf { !project.hasProperty('skipHello') }

◾使用StopExecutionException
如果不能用谓词来表示跳过任务的逻辑，则可以使用 StopExecutionException。如果某个动作引发了此异常，则将跳过该动作的进一步执行以及该任务的任何后续动作的
执行。构建继续执行下一个任务。

// gradle -q myTask
task compile {
    doLast {
        println 'We are doing the compile.'
    }
}

compile.doFirst {
    // Here you would put arbitrary conditions in real life.
    // But this is used in an integration test so we want defined behavior.
    if (true) { throw new StopExecutionException() }
}
task myTask {
    dependsOn('compile')
    doLast {
        println 'I am not affected'
    }
}

◾启用和禁用任务
每个任务都有一个 enabled 默认为的标志 true。将其设置为 false 阻止执行任何任务动作。禁用的任务将标记为“跳过”。


task disableMe {
    doLast {
        println 'This should not be printed if the task is disabled.'
    }
}
disableMe.enabled = false

◾任务超时
每个任务都有一个 timeout 可用于限制其执行时间的属性。当任务达到超时时，其任务执行线程将被中断。该任务将被标记为失败。终结器任务仍将运行。
如果 --continue 使用，其他任务可以在此之后继续运行。不响应中断的任务无法超时。Gradle 的所有内置任务均会及时响应超时


task hangingTask() {
    doLast {
        Thread.sleep(100000)
    }
    timeout = Duration.ofMillis(500)
}

●任务规则
有时您想执行一个任务，该任务的行为取决于较大或无限数量的参数值范围。提供此类任务的一种非常好的表达方式是任务规则：

// gradle -q pingServer1
tasks.addRule("Pattern: ping<ID>") { String taskName ->
    if (taskName.startsWith("ping")) {
        task(taskName) {
            doLast {
                println "Pinging: " + (taskName - 'ping')
            }
        }
    }
}

规则不仅在从命令行调用任务时使用。您还可以在基于规则的任务上创建 dependsOn 关系：

// gradle -q 
groupPing

tasks.addRule("Pattern: ping<ID>") { String taskName ->
    if (taskName.startsWith("ping")) {
        task(taskName) {
            doLast {
                println "Pinging: " + (taskName - 'ping')
            }
        }
    }
}

task groupPing {
    dependsOn pingServer1, pingServer2
}

如果运行“ gradle -q tasks”，将找不到名为“ pingServer1”或“ pingServer2” 的任务，但是此脚本正在根据运行这些任务的请求执行逻辑。

●Finalizer tasks
当计划运行终结任务时，Finalizer tasks 会自动添加到任务图中。
// 
gradle -q taskX

task taskX {
    doLast {
        println 'taskX'
    }
}
task taskY {
    doLast {
        println 'taskY'
    }
}

taskX.finalizedBy taskY

即使终结任务失败，也将执行 Finalizer tasks。


// gradle -q taskX
task taskX {
    doLast {
        println 'taskX'
        throw new RuntimeException()
    }
}
task taskY {
    doLast {
        println 'taskY'
    }
}

taskX.finalizedBy taskY 

运行结果：

Output of gradle -q taskX
> gradle -q taskX
taskX
taskY

FAILURE: Build failed with an exception.

* Where:
Build file '/home/user/gradle/samples/groovy/build.gradle' line: 4

* What went wrong:
Execution failed for task ':taskX'.
> java.lang.RuntimeException (no error message)

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 0s

在构建创建无论构建失败还是成功都必须清除的资源的情况下，终结器任务很有用。这种资源的一个示例是一个Web容器，它在集成测试任务之前启动，并且即使某些测试失败，也应始
终将其关闭。

要指定终结器任务，请使用 Task.finalizedBy（java.lang.Object ...） 方法。此方法接受Task实例，任务名称或 Task.dependsOn（java.lang.Object…） 
接受的任何其他输入